package org.bet.loader;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bet.domain.Assembly;
import org.bet.domain.BioFeatureEntry;
import org.bet.domain.BioFeatureSet;
import org.bet.domain.BioFeatureSet.FeatureType;
import org.bet.utils.Range;

public class GenePredBioFeatureEntryParser implements BioFeatureEntryParser {
	private final Log logger = LogFactory.getLog(getClass());
	
	@Override
	public List<BioFeatureEntry> parseEntries(String line, BioFeatureSet owner) {
		
		try{
			List<BioFeatureEntry> entries = new ArrayList<BioFeatureEntry>();
			if(!StringUtils.isEmpty(line) && owner != null){
				

				String[] tokens = StringUtils.splitPreserveAllTokens(line, '\t');
				
				if(tokens != null && tokens.length >= 12){
					String name   = StringUtils.trim(tokens[0]);
					String chrId = StringUtils.trim(tokens[1]);
					String strand   = StringUtils.trim(tokens[2]);
					Range tsRange  = new Range(Long.parseLong(StringUtils.trim(tokens[3])), 
													 Long.parseLong(StringUtils.trim(tokens[4])));
					Range cdsRange = new Range(Long.parseLong(StringUtils.trim(tokens[5])), 
													 Long.parseLong(StringUtils.trim(tokens[6])));
					
					String proteinId = StringUtils.trim(tokens[10]);
					String alignId = StringUtils.trim(tokens[11]);
					
					int exonCount       = Integer.parseInt(StringUtils.trim(tokens[7]));
				
					String[] exonStarts = StringUtils.split(tokens[8], ',');
					String[] exonEnds   = StringUtils.split(tokens[9], ',');
					
					Range[] exonRanges = new Range[exonCount];
					if(exonCount == ArrayUtils.getLength(exonStarts) &&
					   exonCount == ArrayUtils.getLength(exonEnds)){
						
						for(int i = 0; i < exonCount; i++){
							exonRanges[i] = new Range(Long.parseLong(StringUtils.trim(exonStarts[i])),
													  Long.parseLong(StringUtils.trim(exonEnds[i])));
						}
						
						Assembly assembly = owner.getGenome().getAssemblyByName(chrId);
						if(assembly != null){
							// add gene entry
							BioFeatureEntry entry = new BioFeatureEntry(name, owner.getId());
							entry.setDescription(name);
							entry.setAssembly(assembly);
							entry.setStrand(strand);
							entry.setType(FeatureType.mRNA);
							entry.setRegion(tsRange);
							entry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_PROTEIN_ID, proteinId);
							entry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_ALIGN_ID, alignId);
							
							entries.add(entry);
							
							logger.debug("New entry added==>" + entry);
							
							// component entry for CDS and UTRs, if protein id is not empty
							int nComp = 0;
							
							BioFeatureEntry compEntry = null;						
							if(cdsRange.getEnd() - cdsRange.getStart() > 0 && StringUtils.isNotEmpty(proteinId)){
								// CDS
								compEntry = new BioFeatureEntry(name + "." + (++nComp), owner.getId());
								compEntry.setDescription("CDS of " + name);
								compEntry.setAssembly(assembly);
								compEntry.setStrand(strand);
								compEntry.setType(FeatureType.CDS);
								compEntry.setRegion(cdsRange);
								compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_PROTEIN_ID, proteinId);
								compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_ALIGN_ID, alignId);
								compEntry.getParents().add(entry);

								entries.add(compEntry);
								logger.debug("New component entry of " + entry.getName() + " added==>" + compEntry);
								
								// five prime UTR
								Range fUtrRange = getFivePrimeUTRRange(cdsRange, exonRanges, "+".equals(strand));
								if(fUtrRange != null){
									compEntry = new BioFeatureEntry(name + "." + (++nComp), owner.getId());
									compEntry.setDescription("Five prime UTR of " + name);
									compEntry.setAssembly(assembly);
									compEntry.setStrand(strand);
									compEntry.setType(FeatureType.five_prime_UTR);
									compEntry.setRegion(fUtrRange);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_PROTEIN_ID, proteinId);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_ALIGN_ID, alignId);
									compEntry.getParents().add(entry);
									
									entries.add(compEntry);
									logger.debug("New component entry of " + entry.getName() + " added==>" + compEntry);
									
								}
								else{
									logger.info("No five prime URL:" + name);		
								}
								Range tUtrRange = getThreePrimeUTRRange(cdsRange, exonRanges, "+".equals(strand));
								if(tUtrRange != null){
									compEntry = new BioFeatureEntry(name + "." + (++nComp), owner.getId());
									compEntry.setDescription("Three prime UTR of " + name);
									compEntry.setAssembly(assembly);
									compEntry.setStrand(strand);
									compEntry.setType(FeatureType.three_prime_UTR);
									compEntry.setRegion(tUtrRange);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_PROTEIN_ID, proteinId);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_ALIGN_ID, alignId);
									compEntry.getParents().add(entry);
									
									entries.add(compEntry);
									logger.debug("New component entry of " + entry.getName() + " added==>" + compEntry);									
								}
								else{
									logger.info("No three prime UTR" + chrId);		
								}
							}
							
							// model component for exons	
							if(exonCount > 0){
								
								for(Range exonRange : exonRanges){
									compEntry =  new BioFeatureEntry(name + "." + (++nComp), owner.getId());
									compEntry.setDescription("Exon of " + name);
									compEntry.setAssembly(assembly);
									compEntry.setStrand(strand);
									compEntry.setType(FeatureType.exon);
									compEntry.setRegion(exonRange);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_PROTEIN_ID, proteinId);
									compEntry.addRefAttribute(BioFeatureEntry.REF_ATTR_KEY_ALIGN_ID, alignId);
									compEntry.getParents().add(entry);
									
									entries.add(compEntry);
									logger.debug("New component entry of " + entry.getName() + " added==>" + compEntry);
								}
													
							}
						}
						else{
							logger.warn("Unknown assembly:" + chrId);						
						}
					}
					else{
						logger.warn("Illegal line format, mismatched exon count:" + line);		
					}
				}
				else{
					logger.warn("Illegal line format: " + line);
				}
				
			}
			return entries;
		}
		catch(Exception e){
			logger.error(e);
			throw new RuntimeException("Failed to parse the line: " + line + System.getProperty("line.separator") + e);
		}
	}
	
	public static Range getFivePrimeUTRRange(Range cdsRange, Range[] exonRanges, boolean positiveStrand) {
		Range range = null;
		
		if(cdsRange != null && !ArrayUtils.isEmpty(exonRanges)){
			long start = -1; 
			long end   = -1; 
			
			if(positiveStrand){
				start = exonRanges[0].getStart();
				end   = cdsRange.getStart() - 1;
			}
			else{
				start = cdsRange.getEnd() + 1;
				end   = exonRanges[exonRanges.length - 1].getEnd();
			}
			
			if(end - start > 0){
				range = new Range(start, end);
			}
		}
		
		return range;
	}

	public static Range getThreePrimeUTRRange(Range cdsRange, Range[] exonRanges, boolean positiveStrand) {
		Range range = null;
		
		if(cdsRange != null && !ArrayUtils.isEmpty(exonRanges)){
			long start = -1; 
			long end   = -1; 
			
			if(positiveStrand){
				start =  cdsRange.getEnd() + 1;
				end   = exonRanges[exonRanges.length - 1].getEnd();
			}
			else{
				start = exonRanges[0].getStart();
				end   = cdsRange.getStart() - 1;
			}	
			if(end - start > 0){
				range = new Range(start, end);
			}
		}

		return range;
	}

	@Override
	public boolean supports(FormatType formatType) {
		return FormatType.GenePred.equals(formatType);
	}

}
